/**
 * Copyright 2009-2011 the original author or authors.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.myjung;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.Closeable;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.io.Reader;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.math.BigDecimal;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.channels.FileChannel;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.zip.CRC32;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.SAXParserFactory;

import net.myjung.model.BaseObject;
import net.myjung.model.ExtensibleObject;
import net.myjung.model.Property;
import net.myjung.model.annotation.MdlAttribute;
import net.myjung.model.annotation.MdlClass;
import net.myjung.model.annotation.MdlElement;
import net.myjung.model.annotation.MdlField;
import net.myjung.model.annotation.MdlText;
import net.myjung.util.ListMap;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.ResourceUtils;
import org.springframework.util.StringUtils;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

/**
 * @author Steve M. Jung
 */
public class Myjung {
	private static Log logger = LogFactory.getLog(Myjung.class);

	public static final long TIME_SECOND = 1000;
	public static final long TIME_MINUTE = TIME_SECOND * 60;
	public static final long TIME_HOUR = TIME_MINUTE * 60;
	public static final long TIME_DAY = TIME_HOUR * 24;
	public static final long TIME_WEEK = TIME_DAY * 7;

	public static final String CYCLE_YEAR = "Year";
	public static final String CYCLE_HALF = "Half";
	public static final String CYCLE_QUARTER = "Quarter";
	public static final String CYCLE_MONTH = "Month";
	public static final String CYCLE_WEEK = "Week";
	public static final String CYCLE_DAY = "Day";
	public static final String CYCLE_HOUR = "Hour";
	public static final String CYCLE_MINUTE = "Minute";
	public static final String[] CYCLE = new String[] { CYCLE_YEAR, CYCLE_HALF, CYCLE_QUARTER, CYCLE_MONTH, CYCLE_WEEK, CYCLE_DAY, CYCLE_HOUR,
			CYCLE_MINUTE };

	public static final String DATEFORMATSTRING_XSDDATETIME = "yyyy-MM-dd'T'HH:mm:ss'.'S";
	public static final String DATEFORMATSTRING_XSDSMALLDATETIME = "yyyy-MM-dd'T'HH:mm:ss";
	public static final String DATEFORMATSTRING_XSDDATETIMEZ = "yyyy-MM-dd'T'HH:mm:ss'Z'";
	public static final String DATEFORMATSTRING_XSDDATE = "yyyy-MM-dd";
	public static final SimpleDateFormat DATEFORMAT_XSDDATETIME = new SimpleDateFormat(DATEFORMATSTRING_XSDDATETIME);
	public static final SimpleDateFormat DATEFORMAT_XSDSMALLDATETIME = new SimpleDateFormat(DATEFORMATSTRING_XSDSMALLDATETIME);
	public static final SimpleDateFormat DATEFORMAT_XSDDATETIMEZ = new SimpleDateFormat(DATEFORMATSTRING_XSDDATETIMEZ);
	public static final SimpleDateFormat DATEFORMAT_XSDDATE = new SimpleDateFormat(DATEFORMATSTRING_XSDDATE);
	protected static final SimpleDateFormat DATEFORMAT[] = { DATEFORMAT_XSDDATETIME, DATEFORMAT_XSDSMALLDATETIME, DATEFORMAT_XSDDATETIMEZ,
			DATEFORMAT_XSDDATE, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,S", Locale.US),
			new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US), new SimpleDateFormat("EEE MMM dd HH:mm:ss zzz yyyy", Locale.US),
			new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US), new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US) };

	private static Map<String, Object> threadPropertyMap = new Hashtable<String, Object>();
	public static Object getThreadProperty(String key) {
		String keyId = new StringBuffer(Thread.currentThread().getId() + "").append(".").append(key).toString();
		if (threadPropertyMap.containsKey(keyId))
			return threadPropertyMap.get(keyId);
		return null;
	}
	public static <T> T setThreadProperty(String key, T value) {
		String keyId = new StringBuffer(Thread.currentThread().getId() + "").append(".").append(key).toString();
		if (value == null) {
			if (threadPropertyMap.containsKey(keyId))
				threadPropertyMap.remove(keyId);
		} else {
			threadPropertyMap.put(keyId, value);
		}
		return value;
	}

	public static boolean isEmpty(Object obj) {
		if (obj == null)
			return true;
		if (obj instanceof String) {
			String str = ((String) obj).trim();
			return str.length() == 0 || str.equals("null") || str.equals("undefined");
		} else if (obj instanceof StringBuffer) {
			return isEmpty(obj.toString());
		} else if (obj instanceof Collection<?>) {
			return ((Collection<?>) obj).isEmpty();
		} else if (obj instanceof Object[]) {
			return ((Object[]) obj).length == 0;
		} else if (obj instanceof Map<?, ?>) {
			return ((Map<?, ?>) obj).isEmpty();
		} else if (obj instanceof NamedNodeMap) {
			return ((NamedNodeMap) obj).getLength() == 0;
		} else if (obj instanceof Character) {
			return ((Character) obj) == ' ';
		}
		return false;
	}
	public static boolean isEqual(Object a, Object b) {
		if (a == null) {
			if (b == null)
				return true;
			return false;
		}
		if (b == null)
			return false;
		return a.equals(b);
	}
	public static boolean isNumber(String str) {
		if (str == null)
			return false;
		str = str.trim();
		boolean dotNotFound = true;
		for (int i = 0; i < str.length(); i++) {
			int ascii = (int) str.charAt(i);
			if (i == 0 && ascii == 45)
				continue;
			if (dotNotFound && ascii == 46) {
				dotNotFound = false;
				continue;
			}
			if (ascii < 48 || ascii > 57)
				return false;
		}
		return true;
	}
	public static String toString(Object value) {
		return toString(value, null);
	}
	public static String toString(Object value, String defaultValue) {
		return isEmpty(value) ? defaultValue : value.toString();
	}
	public static String toNull(String value) {
		return toString(value, null);
	}
	public static String toNotNull(String value) {
		return toString(value, "");
	}
	public static boolean toBoolean(Object bool) {
		return toBoolean(bool, false);
	}
	private static String[] trues = new String[] { "true", "yes", "y", "on", "1" };
	private static Set<String> trueSet = toSet(trues);
	public static Boolean toBoolean(Object bool, Boolean defaultValue) {
		if (bool == null)
			return defaultValue;
		if (bool instanceof Boolean)
			return (Boolean) bool;
		return trueSet.contains(bool.toString().trim().toLowerCase());
	}
	public static Integer toInteger(Object value, Integer defaultValue) {
		if (value == null)
			return defaultValue;
		if (value instanceof Integer)
			return (Integer) value;
		String str = value.toString();
		try {
			return new Integer(str);
		} catch (Exception e) {
			return defaultValue;
		}
	}
	public static Long toLong(Object value, Long defaultValue) {
		if (value == null)
			return defaultValue;
		if (value instanceof Long)
			return (Long) value;
		String str = value.toString();
		try {
			return new Long(str);
		} catch (Exception e) {
			return defaultValue;
		}
	}
	public static Float toFloat(Object value, Float defaultValue) {
		if (value == null)
			return defaultValue;
		if (value instanceof Float)
			return (Float) value;
		String str = value.toString();
		try {
			return new Float(str);
		} catch (Exception e) {
			return defaultValue;
		}
	}
	public static Double toDouble(Object value, Double defaultValue) {
		if (value == null)
			return defaultValue;
		if (value instanceof Double)
			return (Double) value;
		String str = value.toString();
		try {
			return new Double(str);
		} catch (Exception e) {
			return defaultValue;
		}
	}
	public static <T> List<T> toList(T... objs) {
		List<T> list = new ArrayList<T>();
		if (!isEmpty(objs)) {
			for (T obj : objs)
				list.add(obj);
		}
		return list;
	}
	public static <T> Set<T> toSet(T... objs) {
		Set<T> set = new HashSet<T>();
		if (!isEmpty(objs)) {
			for (T obj : objs)
				set.add(obj);
		}
		return set;
	}

	public static String toUpperCase(String str, int index) {
		if (Myjung.isEmpty(str))
			return str;
		String rStr = str.charAt(index) + "";
		return str.replaceFirst(rStr, rStr.toUpperCase());
	}
	public static String toLowerCase(String str, int index) {
		if (Myjung.isEmpty(str))
			return str;
		String rStr = str.charAt(index) + "";
		return str.replaceFirst(rStr, rStr.toLowerCase());
	}
	public static String newId() {
		return UUIdGenerator.getInstance().generateId();
	}
	public static String newUId() {
		return UUIdGenerator.getInstance().generateUId();
	}

	public static <T> T populate(Object from, T to) {
		return populate(from, to, false);
	}

	/**
	 * Populates 'to object' from 'from object' whose field's name are same.
	 * 
	 * @param from
	 *            object to get value from
	 * @param to
	 *            object to populate
	 * @param ignoreNullValue
	 *            ignore null value or not.
	 */
	private static <T> T populate(Object from, T to, boolean ignoreNullValue) {
		if (from == null || to == null)
			return to;
		Class<?> fromClass = from.getClass();
		Class<?> toClass = to.getClass();
		Myjung.populate(from, to, ignoreNullValue, fromClass, toClass, toClass.isAssignableFrom(fromClass), new HashSet<String>());
		return to;
	}
	private static void populate(Object from, Object to, boolean ignoreNullValue, Class<?> fromClass, Class<?> toClass,
			boolean isToClassAssignableByFromClass, Set<String> set) {
		Field[] fields = toClass.getDeclaredFields();
		if (!Myjung.isEmpty(fields)) {
			for (Field toField : fields) {
				// validation
				if (Modifier.isStatic(toField.getModifiers()))
					continue;
				String fieldName = toField.getName();
				if (set.contains(fieldName))
					continue;
				set.add(fieldName);

				// get from field
				Field fromField = null;
				if (isToClassAssignableByFromClass) {
					fromField = toField;
				} else {
					fromField = Myjung.getField(fromClass, fieldName);
					if (fromField == null)
						continue;
				}

				// set value
				setValue(from, to, fromField, toField, ignoreNullValue);
			}
		}
		toClass = toClass.getSuperclass();
		if (toClass == Object.class)
			return;
		populate(from, to, ignoreNullValue, fromClass, toClass, isToClassAssignableByFromClass ? true : toClass.isAssignableFrom(fromClass), set);
	}
	private static void setValue(Object from, Object to, Field fromField, Field toField, boolean ignoreNullValue) {
		// get from value
		if (!fromField.isAccessible())
			fromField.setAccessible(true);
		Object value = null;
		try {
			value = fromField.get(from);
		} catch (Exception e) {
		}
		if (ignoreNullValue && value == null)
			return;

		// set to value
		if (!toField.isAccessible())
			toField.setAccessible(true);
		try {
			toField.set(to, toValueAsField(value, toField, true));
		} catch (Exception e) {
		}
	}
	@SuppressWarnings("unchecked")
	private static Object clone(Object obj) throws Exception {
		if (obj == null)
			return obj;

		if (obj instanceof BaseObject) {
			return ((BaseObject) obj).clone();
		} else if (obj instanceof Collection) {
			Collection<Object> col = (Collection<Object>) obj;
			Collection<Object> newCol = (Collection<Object>) newInstance(col);
			for (Object item : col)
				newCol.add(clone(item));
			return newCol;
		} else if (obj instanceof Map) {
			Map<Object, Object> map = (Map<Object, Object>) obj;
			Map<Object, Object> newMap = (Map<Object, Object>) newInstance(map);
			Collection<Object> keys = map instanceof ListMap ? ((ListMap<Object, Object>) map).keys() : map.keySet();
			for (Object key : keys) {
				Object value = map.get(key);
				newMap.put(clone(key), clone(value));
			}
			return newMap;
		} else if (obj instanceof Date) {
			return ((Date) obj).clone();
		}

		Class<?> objType = obj.getClass();
		if (objType.getName().startsWith("java."))
			return obj;
		Object newObj = populate(obj, objType.newInstance(), false);
		if (newObj == null)
			return obj;
		return newObj;
	}
	private static Object newInstance(Object obj) throws Exception {
		Class<?> objType = obj.getClass();
		return objType.newInstance();
	}

	public static Object getValue(Object obj, String fieldName) {
		if (obj == null)
			return null;
		Field field = getField(obj.getClass(), fieldName);
		try {
			return field == null ? null : field.get(obj);
		} catch (Exception e) {
		}
		return null;
	}
	public static Object setValue(Object obj, String fieldName, Object value) {
		if (obj == null)
			return null;
		Field field = getField(obj.getClass(), fieldName);
		if (field == null)
			return null;
		try {
			value = toValueAsField(value, field, false);
			field.set(obj, value);
			return value;
		} catch (Exception e) {
			return null;
		}
	}
	private static Object toValueAsField(Object value, Field field, boolean clone) throws Exception {
		if (value == null)
			return value;
		Class<?> type = field.getType();
		boolean assignable = type.isAssignableFrom(value.getClass());
		if (assignable && primitiveTypeList.contains(type))
			return value;
		Class<?> valueType = value.getClass();
		if (value instanceof BigDecimal) {
			BigDecimal bc = (BigDecimal) value;
			if (type == Integer.class || type == int.class)
				return bc.intValue();
			else if (type == Long.class || type == long.class)
				return bc.longValue();
			else if (type == Float.class || type == float.class)
				return bc.floatValue();
			else if (type == Double.class || type == double.class)
				return bc.doubleValue();
		} else if (type == String.class) {
			return value.toString();
		} else if (type == Character.class || type == char.class) {
			return Myjung.isEmpty(value.toString()) ? ' ' : value.toString().charAt(0);
		} else if (Collection.class.isAssignableFrom(type) && Collection.class.isAssignableFrom(valueType)) {
			@SuppressWarnings("unchecked")
			Collection<Object> col = (Collection<Object>) value;
			@SuppressWarnings("unchecked")
			Collection<Object> newCol = (Collection<Object>) type.newInstance();
			if (field.getGenericType() instanceof ParameterizedType) {
				Class<?> paramType = (Class<?>) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
				for (Object item : col) {
					if (item == null || paramType.isAssignableFrom(item.getClass())) {
						newCol.add(clone ? clone(item) : item);
						continue;
					}
					newCol.add(populate(item, paramType.newInstance(), false));
				}
			} else {
				for (Object item : col)
					newCol.add(clone ? clone(item) : item);
			}
			return newCol;
		} else if (Map.class.isAssignableFrom(type) && Collection.class.isAssignableFrom(valueType)) {
			@SuppressWarnings("unchecked")
			Map<Object, Object> map = (Map<Object, Object>) value;
			@SuppressWarnings("unchecked")
			Map<Object, Object> newMap = (Map<Object, Object>) newInstance(map);
			Collection<Object> keys = map instanceof ListMap ? ((ListMap<Object, Object>) map).keys() : map.keySet();
			for (Object key : keys) {
				Object v = map.get(key);
				newMap.put(key, clone ? clone(v) : v);
			}
			return newMap;
		} else if (!assignable) {
			try {
				return populate(value, type.newInstance(), false);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return value;
	}
	public static Field getField(Class<?> clazz, String name) {
		if (clazz == Object.class || clazz == BaseObject.class)
			return null;
		Field field = null;
		try {
			field = clazz.getDeclaredField(name);
		} catch (SecurityException e) {
			return null;
		} catch (NoSuchFieldException e) {
			return getField(clazz.getSuperclass(), name);
		}
		if (!field.isAccessible())
			field.setAccessible(true);
		return field;
	}
	public static Field[] getFields(Class<?> clazz) {
		List<Field> list = populateFieldList(new ArrayList<Field>(), null, clazz);
		return list.toArray(new Field[list.size()]);
	}
	private static List<Field> populateFieldList(List<Field> list, Set<String> set, Class<?> clazz) {
		if (clazz == Object.class || clazz == BaseObject.class)
			return list;
		Field[] fields = clazz.getDeclaredFields();
		if (isEmpty(fields))
			return list;
		if (set == null)
			set = new HashSet<String>();
		for (Field field : fields) {
			if (Modifier.isStatic(field.getModifiers()) || set.contains(field.getName()))
				continue;
			list.add(field);
			set.add(field.getName());
		}
		populateFieldList(list, set, clazz.getSuperclass());
		return list;
	}

	public static String escapeXml(String str) {
		if (str == null)
			return null;
		str = StringUtils.replace(str, "&", "&amp;");
		str = StringUtils.replace(str, "<", "&lt;");
		str = StringUtils.replace(str, ">", "&gt;");
		str = StringUtils.replace(str, "\"", "&quot;");
		str = StringUtils.replace(str, "'", "&apos;");
		return str;
	}
	public static String unescapeXml(String str) {
		if (str == null)
			return null;
		str = StringUtils.replace(str, "&apos;", "'");
		str = StringUtils.replace(str, "&quot;", "\"");
		str = StringUtils.replace(str, "&gt;", ">");
		str = StringUtils.replace(str, "&lt;", "<");
		str = StringUtils.replace(str, "&amp;", "&");
		return str;
	}
	public static int toDay(long milli) {
		long day = milli / TIME_DAY;
		long other = milli % TIME_DAY;
		if (other > 0)
			day++;
		return (int) day;
	}
	public static Date toFromDate(Date date, String cycle) {
		if (date == null || cycle == null)
			return date;
		Calendar cal = Calendar.getInstance();
		cal.setTime(date);
		int year = cal.get(Calendar.YEAR);
		int month = cal.get(Calendar.MONTH);
		int day = cal.get(Calendar.DAY_OF_MONTH);
		int hour = cal.get(Calendar.HOUR_OF_DAY);
		int minute = cal.get(Calendar.MINUTE);
		cal.set(Calendar.YEAR, year);
		cal.set(Calendar.MONTH, 0);
		cal.set(Calendar.DAY_OF_MONTH, 1);
		cal.set(Calendar.HOUR_OF_DAY, 0);
		cal.set(Calendar.MINUTE, 0);
		cal.set(Calendar.SECOND, 0);
		cal.set(Calendar.MILLISECOND, 0);
		if (cycle.equals(CYCLE_YEAR)) {
		} else if (cycle.equals(CYCLE_HALF)) {
			if (month <= Calendar.JUNE) {
				cal.set(Calendar.MONTH, Calendar.JANUARY);
			} else {
				cal.set(Calendar.MONTH, Calendar.JULY);
			}
		} else if (cycle.equals(CYCLE_QUARTER)) {
			if (month <= Calendar.MARCH) {
				cal.set(Calendar.MONTH, Calendar.JANUARY);
			} else if (month <= Calendar.JUNE) {
				cal.set(Calendar.MONTH, Calendar.APRIL);
			} else if (month <= Calendar.SEPTEMBER) {
				cal.set(Calendar.MONTH, Calendar.JULY);
			} else {
				cal.set(Calendar.MONTH, Calendar.OCTOBER);
			}
			return cal.getTime();
		} else if (cycle.equals(CYCLE_MONTH)) {
			cal.set(Calendar.MONTH, month);
		} else if (cycle.equals(CYCLE_WEEK)) {
			// TODO
			cal.set(Calendar.MONTH, month);
			cal.set(Calendar.DAY_OF_MONTH, 1);
			cal.set(Calendar.HOUR_OF_DAY, 0);
			cal.set(Calendar.MINUTE, 0);
		} else if (cycle.equals(CYCLE_DAY)) {
			cal.set(Calendar.MONTH, month);
			cal.set(Calendar.DAY_OF_MONTH, day);
		} else if (cycle.equals(CYCLE_HOUR)) {
			cal.set(Calendar.MONTH, month);
			cal.set(Calendar.DAY_OF_MONTH, day);
			cal.set(Calendar.HOUR_OF_DAY, hour);
		} else if (cycle.equals(CYCLE_MINUTE)) {
			cal.set(Calendar.MONTH, month);
			cal.set(Calendar.DAY_OF_MONTH, day);
			cal.set(Calendar.HOUR_OF_DAY, hour);
			cal.set(Calendar.MINUTE, minute);
		}
		return cal.getTime();
	}
	public static Date toToDate(Date date, String cycle) {
		if (date == null || cycle == null)
			return date;
		Calendar cal = Calendar.getInstance();
		cal.setTime(date);
		int year = cal.get(Calendar.YEAR);
		int month = cal.get(Calendar.MONTH);
		int day = cal.get(Calendar.DAY_OF_MONTH);
		int hour = cal.get(Calendar.HOUR_OF_DAY);
		int minute = cal.get(Calendar.MINUTE);
		cal.set(Calendar.YEAR, year);
		cal.set(Calendar.MONTH, 11);
		cal.set(Calendar.DAY_OF_MONTH, 31);
		cal.set(Calendar.HOUR_OF_DAY, 23);
		cal.set(Calendar.MINUTE, 59);
		cal.set(Calendar.SECOND, 59);
		cal.set(Calendar.MILLISECOND, 997);
		if (cycle.equals(CYCLE_YEAR)) {
		} else if (cycle.equals(CYCLE_HALF)) {
			if (month <= Calendar.JUNE) {
				cal.set(Calendar.MONTH, Calendar.JUNE);
				cal.set(Calendar.DAY_OF_MONTH, 30);
			} else {
				cal.set(Calendar.MONTH, Calendar.DECEMBER);
				cal.set(Calendar.DAY_OF_MONTH, 31);
			}
		} else if (cycle.equals(CYCLE_QUARTER)) {
			if (month <= Calendar.MARCH) {
				cal.set(Calendar.MONTH, Calendar.MARCH);
				cal.set(Calendar.DAY_OF_MONTH, 31);
			} else if (month <= Calendar.JUNE) {
				cal.set(Calendar.MONTH, Calendar.JUNE);
				cal.set(Calendar.DAY_OF_MONTH, 30);
			} else if (month <= Calendar.SEPTEMBER) {
				cal.set(Calendar.MONTH, Calendar.SEPTEMBER);
				cal.set(Calendar.DAY_OF_MONTH, 30);
			} else {
				cal.set(Calendar.MONTH, Calendar.DECEMBER);
				cal.set(Calendar.DAY_OF_MONTH, 31);
			}
			return cal.getTime();
		} else if (cycle.equals(CYCLE_MONTH)) {
			cal.set(Calendar.MONTH, month);
			if (month == Calendar.FEBRUARY) {
				if (year % 4 == 0) {
					cal.set(Calendar.DAY_OF_MONTH, 29);
				} else {
					cal.set(Calendar.DAY_OF_MONTH, 28);
				}
			} else if (month == Calendar.APRIL || month == Calendar.JUNE || month == Calendar.SEPTEMBER || month == Calendar.NOVEMBER) {
				cal.set(Calendar.DAY_OF_MONTH, 30);
			} else {
				cal.set(Calendar.DAY_OF_MONTH, 31);
			}
		} else if (cycle.equals(CYCLE_WEEK)) {
			// TODO
			cal.set(Calendar.MONTH, month);
			cal.set(Calendar.DAY_OF_MONTH, 1);
			cal.set(Calendar.HOUR_OF_DAY, 0);
			cal.set(Calendar.MINUTE, 0);
		} else if (cycle.equals(CYCLE_DAY)) {
			cal.set(Calendar.MONTH, month);
			cal.set(Calendar.DAY_OF_MONTH, day);
		} else if (cycle.equals(CYCLE_HOUR)) {
			cal.set(Calendar.MONTH, month);
			cal.set(Calendar.DAY_OF_MONTH, day);
			cal.set(Calendar.HOUR_OF_DAY, hour);
		} else if (cycle.equals(CYCLE_MINUTE)) {
			cal.set(Calendar.MONTH, month);
			cal.set(Calendar.DAY_OF_MONTH, day);
			cal.set(Calendar.HOUR_OF_DAY, hour);
			cal.set(Calendar.MINUTE, minute);
		}
		return cal.getTime();
	}
	public static Date toDate(String dateString, Date defaultValue) {
		if (dateString == null || dateString.trim().length() == 0)
			return defaultValue;
		dateString = dateString.trim();

		Date date = null;
		for (int i = 0; i < DATEFORMAT.length; i++) {
			try {
				SimpleDateFormat dateFormat = DATEFORMAT[i];
				date = dateFormat.parse(dateString);
				break;
			} catch (Exception e) {
			}
		}
		return date == null ? defaultValue : date;
	}
	public static Date toDate(String dateString) {
		return toDate(dateString, null);
	}
	public static String toXsdDateString(Date date) {
		if (date == null)
			return null;
		String dateString = DATEFORMAT_XSDDATE.format(date);
		;
		return dateString;
	}
	public static String toXsdDateTimeString(Date date) {
		if (date == null)
			return null;
		return DATEFORMAT_XSDDATETIME.format(date);
	}
	public static String toDateString(Date date) {
		if (date == null)
			return null;

		DateInfo dateInfo = new DateInfo(date);

		String year = dateInfo.getYear();
		String month = dateInfo.getMonth();
		String day = dateInfo.getDay();
		String hour = dateInfo.getHour();
		String minute = dateInfo.getMinute();
		String second = dateInfo.getSecond();
		String millisecond = dateInfo.getMillisecond();

		StringBuffer dateBuf = new StringBuffer();
		dateBuf.append(year).append("-").append(month).append("-").append(day);
		dateBuf.append(" ").append(hour).append(":").append(minute).append(":").append(second).append(".").append(millisecond);

		return dateBuf.toString();
	}

	public static String toStackTraceString(Throwable t) {
		if (t == null)
			return null;
		StringWriter sWriter = new StringWriter();
		PrintWriter pWriter = new PrintWriter(sWriter, true);
		t.printStackTrace(pWriter);
		return sWriter.toString();
	}

	///////////////////////////FROM////////////////////////////////////////////
	private static final String FIELDCAT_ATTRMAP = "attributeMap";
	private static final String FIELDCAT_TEXTFIELD = "textField";
	private static final String FIELDCAT_ELEMMAP = "elementMap";
	private static final String FIELDCAT_SINGULARELEMMAP = "singularElementMap";

	private static final String LEVEL_LIMIT = "10";
	/**
	 * ALL depth datum
	 */
	public static final String LEVEL_ALL = "all";
	/**
	 * Collection type fields will not be wrapped from multiple name XML
	 * elements when you get XML string
	 */
	public static final String LEVEL_REDUCED = "reduced";
	/**
	 * Only primitive type will be considered. (Big datum such as text type data
	 * will also be considered.)
	 */
	public static final String LEVEL_FLAT = "flat";
	/**
	 * Only primitive type will be considered. (Big datum such as text type data
	 * will not be considered, too.)
	 */
	public static final String LEVEL_LITE = "lite";

	private static final String TYPE_CREATOR = "creator";
	private static final String TYPE_CREATIONDATE = "creationDate";
	private static final String TYPE_TEXT = "text";

	public static String toXMLString(Object obj, boolean includeXMLDeclaration) {
		return toXMLString(obj, includeXMLDeclaration, null, null, "1");
	}
	public static String toXMLString(Object obj, boolean includeXMLDeclaration, String name, String tab, String level) {
		StringBuffer buf = new StringBuffer();

		List<String> dtds = includeXMLDeclaration ? new ArrayList<String>() : null;
		appendXMLString(buf, obj, name, tab, level, dtds);
		if (!includeXMLDeclaration)
			return buf.toString();

		StringBuffer xmlBuf = new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n");
		if (!Myjung.isEmpty(dtds)) {
			for (String dtd : dtds) {
				dtd = dtd.trim();
				if (!dtd.startsWith("<!DOCTYPE"))
					dtd = "<!DOCTYPE " + dtd;
				if (!dtd.endsWith(">"))
					dtd += ">";
				xmlBuf.append(dtd).append("\r\n");
			}
		}
		return xmlBuf.append(buf).toString();
	}

	public static <T> T toObject(Class<T> clazz, String xml) throws Exception {
		return populate(xml, clazz.newInstance(), LEVEL_LIMIT);
	}
	public static <T> T populate(String from, T to, String level) {
		// TODO
		//		populateBySax(from, to, level);
		if (Myjung.isEmpty(from))
			return to;
		try {
			return populate(Myjung.toDocument(from).getDocumentElement(), to, level);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return to;
	}
	/**
	 * Populates the 'to' parameter from 'from' parameter.<br>
	 * The class of from and to must be same.
	 * 
	 * @param from
	 *            The object from
	 * @param to
	 *            The object to
	 * @param level
	 *            The depth level (refer to the LEVEL constants of
	 *            net.myjung.model.BaseObject class.)
	 * @throws Exception
	 */
	@SuppressWarnings("unchecked")
	public static void populate(Object from, Object to, String level) throws Exception {
		if (to == null || from == null || to.getClass() != from.getClass())
			return;
		Map<String, Object> catFieldMap = Myjung.categorizeFields(to.getClass(), level, false);
		ListMap<String, Field> attrMap = (ListMap<String, Field>) catFieldMap.get(FIELDCAT_ATTRMAP);
		Field textField = (Field) catFieldMap.get(FIELDCAT_TEXTFIELD);
		ListMap<String, Field> elemMap = (ListMap<String, Field>) catFieldMap.get(FIELDCAT_ELEMMAP);
		populate(from, to, attrMap);
		populate(from, to, textField);
		populate(from, to, elemMap);
	}

	@SuppressWarnings("unchecked")
	private static void appendXMLString(StringBuffer buf, Object obj, String name, String tab, String level, List<String> dtds) {
		/**
		 * preset
		 */
		if (Myjung.isEmpty(level) || level.equals(LEVEL_ALL))
			level = LEVEL_LIMIT;
		else if (Myjung.isNumber(level) && Integer.parseInt(level) <= 0)
			level = LEVEL_FLAT;
		Map<String, Object> catFieldMap = categorizeFields(obj.getClass(), level, true);
		if (dtds != null) {
			String dtd = getModelClassDtd(obj.getClass());
			if (dtd != null && !dtds.contains(dtd))
				dtds.add(dtd);
		}
		ListMap<String, Field> attrMap = (ListMap<String, Field>) catFieldMap.get(FIELDCAT_ATTRMAP);
		Field textField = (Field) catFieldMap.get(FIELDCAT_TEXTFIELD);
		ListMap<String, Field> elemMap = (ListMap<String, Field>) catFieldMap.get(FIELDCAT_ELEMMAP);
		boolean textListEmpty = textField == null;
		boolean elemListEmpty = Myjung.isEmpty(elemMap);
		List<Field> attrList = Myjung.isEmpty(attrMap) ? null : (List<Field>) attrMap.values();
		List<Field> elemList = elemListEmpty ? null : (List<Field>) elemMap.values();
		if (Myjung.isEmpty(name))
			name = getModelClassName(obj.getClass());
		if (tab == null)
			tab = "";

		/**
		 * start
		 */
		buf.append(tab).append("<").append(name);

		/**
		 * attributes
		 */
		appendAttributesString(buf, obj, attrList);

		/**
		 * after attributes
		 */
		if (textListEmpty && elemListEmpty) {
			buf.append(" />");
			return;
		}

		/**
		 * text
		 */
		if (textListEmpty) {
			buf.append(">");
		} else {
			String fieldValue = getTextValue(obj, textField);
			if (fieldValue == null) {
				if (elemListEmpty) {
					buf.append(" />");
					return;
				}
				buf.append(">");
			} else {
				buf.append(">").append(fieldValue);
			}
		}

		/**
		 * after text
		 */
		if (elemListEmpty) {
			buf.append("</").append(name).append(">");
			return;
		}

		/**
		 * elements
		 */
		String tab2 = tab + "\t";
		int len = buf.length();
		Field preField = null;
		Map<String, String> singularElemMap = (Map<String, String>) catFieldMap.get(FIELDCAT_SINGULARELEMMAP);
		for (Field field : elemList) {
			if (preField == field)
				continue;
			preField = field;
			appendElementString(buf, obj, field, singularElemMap, tab2, level, dtds);
		}
		if (len != buf.length())
			buf.append("\r\n").append(tab);

		/**
		 * end
		 */
		buf.append("</").append(name).append(">");
	}
	protected static void appendAttributesString(StringBuffer buf, Object obj, List<Field> attrList) {
		if (Myjung.isEmpty(attrList))
			return;
		String value;
		Field preField = null;
		for (Field field : attrList) {
			if (preField == field)
				continue;
			preField = field;
			value = getTextValue(obj, field);
			if (value == null)
				continue;
			buf.append(" ").append(toAttributeName(field)).append("=\"").append(value).append("\"");
		}
	}
	private static void appendElementString(StringBuffer buf, Object obj, Field field, Map<String, String> singularElemMap, String tab, String level,
			List<String> dtds) {
		Object value = getObjectValue(obj, field);
		if (value == null)
			return;

		if (tab == null)
			tab = "";

		appendValueElementString(buf, value, field, null, singularElemMap, tab, level, dtds);
	}
	private static void appendValueElementString(StringBuffer buf, Object value, Field field, String name, Map<String, String> singularElemMap,
			String tab, String level, List<String> dtds) {
		if (Myjung.isEmpty(name)) {
			if (field == null)
				return;
			name = toElementName(field);
		}

		/**
		 * check expected object types
		 */
		if (value instanceof BaseObject) {
			appendSingularElementString(buf, value, field, tab, level, dtds);
			return;
		} else if (value instanceof Collection) {
			Collection<?> col = (Collection<?>) value;
			boolean hasWrapper = !level.equals(LEVEL_REDUCED) && singularElemMap.containsValue(name);
			if (Myjung.isEmpty(col)) {
				if (hasWrapper)
					buf.append("\r\n").append(tab).append("<").append(name).append(" />");
				return;
			}
			String tab2 = hasWrapper ? tab + "\t" : tab;
			if (hasWrapper)
				buf.append("\r\n").append(tab).append("<").append(name).append(">");
			for (Object obj : col)
				appendValueElementString(buf, obj, field, getSingularName(obj.getClass().getName(), field), singularElemMap, tab2, level, dtds);
			if (hasWrapper)
				buf.append("\r\n").append(tab).append("</").append(name).append(">");
			return;
		} else if (value instanceof ListMap) {
			@SuppressWarnings("unchecked")
			ListMap<Object, Object> map = (ListMap<Object, Object>) value;
			for (Object key : map.keys()) {
				Object obj = map.get(key);
				if (obj == null)
					continue;
				appendValueElementString(buf, obj, null, key.toString(), singularElemMap, tab, level, dtds);
			}
			return;
		} else if (value instanceof Map) {
			// TODO Map to element
			return;
		}

		/**
		 * check primitive types
		 */
		for (Class<?> type : getSupportingPrimitiveTypes()) {
			if (type.isInstance(value)) {
				buf.append("\r\n").append(tab).append("<").append(name).append(">");
				buf.append(toTextValue(value));
				buf.append("</").append(name).append(">");
				return;
			}
		}

		/**
		 * other types
		 */
		appendSingularElementString(buf, value, field, tab, level, dtds);
	}
	private static void appendSingularElementString(StringBuffer buf, Object obj, Field field, String tab, String level, List<String> dtds) {
		String name = getSingularName(obj.getClass().getName(), field);
		buf.append("\r\n");
		if (Myjung.isNumber(level)) {
			int levelInt = Integer.parseInt(level) - 1;
			if (levelInt <= 0)
				level = LEVEL_FLAT;
			else
				level = levelInt + "";
		}
		appendXMLString(buf, obj, name, tab, level, dtds);
	}
	private static String getSingularName(String className, Field field) {
		MdlElement ann = field.getAnnotation(MdlElement.class);
		if (ann == null)
			return field.getName();

		String name = Myjung.toString(ann.singular(), ann.name());
		if (!Myjung.isEmpty(name))
			return name;

		if (Myjung.isEmpty(ann.members()))
			return field.getName();

		String[] members = ann.members();
		for (String member : members) {
			String[] elemInfos = StringUtils.tokenizeToStringArray(member, ",");
			if (elemInfos[1].equals(className)) {
				name = Myjung.isEmpty(elemInfos[0]) ? field.getName() : elemInfos[0];
				break;
			}
		}
		return name;
	}
	//	private static void appendMapElementString(StringBuffer buf, ListMap<?,?> map, String tab, String level, List<String> dtds) {
	//		for (Object key : map.keys()) {
	//			Object obj = map.get(key);
	//			if (obj == null)
	//				continue;
	////			appendElementString(buf, (String) key, obj, tab, level);
	//		}
	//	}
	private static Object getObjectValue(Object obj, Field field) {
		try {
			if (!field.isAccessible())
				field.setAccessible(true);
			return field.get(obj);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}
	private static String getTextValue(Object obj, Field field) {
		return toTextValue(getObjectValue(obj, field));
	}
	private static String toTextValue(Object primitiveTypeValue) {
		if (primitiveTypeValue == null)
			return null;
		if (primitiveTypeValue instanceof String) {
			return Myjung.escapeXml(primitiveTypeValue.toString());
		} else if (primitiveTypeValue instanceof Date) {
			return Myjung.toXsdDateTimeString((Date) primitiveTypeValue);
		} else {
			return primitiveTypeValue.toString();
		}
	}
	private static String getModelClassName(Class<?> clazz) {
		MdlClass ann = clazz.getAnnotation(MdlClass.class);
		return Myjung.toString(ann == null ? null : ann.name(), Myjung.toLowerCase(clazz.getSimpleName(), 0));
	}
	private static String getModelClassDtd(Class<?> clazz) {
		MdlClass ann = clazz.getAnnotation(MdlClass.class);
		return ann == null ? null : Myjung.toNull(ann.dtd());
	}
	private static String toElementName(Field field) {
		MdlElement ann = field.getAnnotation(MdlElement.class);
		return ann == null || Myjung.isEmpty(ann.name()) ? field.getName() : ann.name();
	}
	private static String toAttributeName(Field field) {
		MdlAttribute ann = field.getAnnotation(MdlAttribute.class);
		return Myjung.isEmpty(ann.name()) ? field.getName() : ann.name();
	}

	private static void populate(Object from, Object to, Map<String, Field> map) throws Exception {
		if (Myjung.isEmpty(map))
			return;
		for (Field field : map.values())
			populate(from, to, field);
	}
	private static void populate(Object from, Object to, Field field) throws Exception {
		if (field == null)
			return;
		if (!field.isAccessible())
			field.setAccessible(true);
		field.set(to, field.get(from));
	}
	private static Map<String, Object> categorizeFields(Class<?> clazz, String level, boolean includeNoChange) {
		ListMap<String, Field> attrMap = new ListMap<String, Field>();
		ListMap<String, Field> elemMap = new ListMap<String, Field>();
		Map<String, String> singularElemMap = new HashMap<String, String>();

		Map<String, Object> map = new HashMap<String, Object>();
		map.put(FIELDCAT_ATTRMAP, attrMap);
		map.put(FIELDCAT_ELEMMAP, elemMap);
		map.put(FIELDCAT_SINGULARELEMMAP, singularElemMap);

		if (level == null)
			level = LEVEL_LIMIT;
		else if (Myjung.isNumber(level) && Integer.parseInt(level) <= 0)
			level = LEVEL_FLAT;

		populateCategorizedFields(map, clazz, level, includeNoChange);

		return map;
	}

	@SuppressWarnings("unchecked")
	private static void populateCategorizedFields(Map<String, Object> map, Class<?> clazz, String level, boolean includeNoChange) {
		Class<?> superClass = clazz.getSuperclass();
		if (superClass != Object.class && superClass != BaseObject.class)
			populateCategorizedFields(map, superClass, level, includeNoChange);

		Field[] fields = clazz.getDeclaredFields();
		if (Myjung.isEmpty(fields))
			return;

		ListMap<String, Field> attrMap = (ListMap<String, Field>) map.get(FIELDCAT_ATTRMAP);
		Field textField = null;
		ListMap<String, Field> elemMap = (ListMap<String, Field>) map.get(FIELDCAT_ELEMMAP);
		Map<String, String> singularElemMap = (Map<String, String>) map.get(FIELDCAT_SINGULARELEMMAP);

		MdlAttribute attrAnn = null;
		MdlElement elemAnn = null;
		MdlText textAnn = null;
		MdlField fieldAnn = null;
		boolean isAttribute = false;
		boolean isText = false;
		boolean isElement = false;
		String name;
		for (Field field : fields) {
			if (Modifier.isStatic(field.getModifiers()))
				continue;

			attrAnn = field.getAnnotation(MdlAttribute.class);
			elemAnn = field.getAnnotation(MdlElement.class);
			textAnn = field.getAnnotation(MdlText.class);
			fieldAnn = field.getAnnotation(MdlField.class);
			isAttribute = attrAnn != null;
			isText = textAnn != null;
			isElement = elemAnn != null || (attrAnn == null && textAnn == null && fieldAnn == null);
			name = field.getName();

			if (isAttribute) {
				String type = attrAnn.type();
				if (includeNoChange || (!type.startsWith("id") && !TYPE_CREATOR.equals(type) && !TYPE_CREATIONDATE.equals(type))) {
					if (!Myjung.isEmpty(attrAnn.name()))
						attrMap.put(attrAnn.name(), field);
					attrMap.put(name, field);
				}
			}

			if (isText) {
				String type = textAnn.type();
				if ((!LEVEL_LITE.equals(level) || !textAnn.type().equals(TYPE_TEXT))
						&& (includeNoChange || (!type.startsWith("id") && !TYPE_CREATOR.equals(type) && !TYPE_CREATIONDATE.equals(type))))
					textField = field;
			}

			if (isElement) {
				String type = elemAnn == null ? "" : elemAnn.type();
				if (LEVEL_LITE.equals(level)) {
					if (type.equals(TYPE_TEXT) || !getSupportingPrimitiveTypes().contains(field.getType()))
						continue;
				} else if (LEVEL_FLAT.equals(level) && !getSupportingPrimitiveTypes().contains(field.getType())) {
					continue;
				}
				if (includeNoChange || (!type.startsWith("id") && !TYPE_CREATOR.equals(type) && !TYPE_CREATIONDATE.equals(type))) {
					if (elemAnn != null) {
						if (!Myjung.isEmpty(elemAnn.name()))
							elemMap.put(elemAnn.name(), field);
						if (!Myjung.isEmpty(elemAnn.singular()))
							singularElemMap.put(elemAnn.singular(), name);
						if (!Myjung.isEmpty(elemAnn.members())) {
							String[] members = elemAnn.members();
							for (String member : members) {
								String[] elemInfos = StringUtils.tokenizeToStringArray(member, ",");
								String elemSingular = elemInfos[0];
								if (Myjung.isEmpty(elemSingular))
									continue;
								singularElemMap.put(elemSingular, name);
							}
						}
					}
					elemMap.put(name, field);
				}
			}
		}
		if (textField != null)
			map.put(FIELDCAT_TEXTFIELD, textField);
	}
	private static void setFieldValue(Object obj, Field field, Node node) {
		setFieldValue(obj, field, toValue(field.getType(), node));
	}
	private static void setFieldValue(Object obj, Field field, Object value) {
		try {
			if (!field.isAccessible())
				field.setAccessible(true);
			field.set(obj, value);
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
	}
	private static String toNodeValue(Node node) {
		short nodeType = node.getNodeType();
		if (nodeType == Node.TEXT_NODE || nodeType == Node.CDATA_SECTION_NODE)
			return node.getNodeValue();

		NodeList elemList = node.getChildNodes();
		if (elemList == null || elemList.getLength() == 0)
			return "";
		if (elemList != null && elemList.getLength() != 0) {
			Node elem;
			short elemNodeType;
			for (int j = 0; j < elemList.getLength(); j++) {
				elem = elemList.item(j);
				elemNodeType = elem.getNodeType();
				if (elemNodeType != Node.TEXT_NODE && elemNodeType != Node.CDATA_SECTION_NODE)
					continue;
				return elem.getNodeValue();
			}
		}
		return "";
	}

	private static Object toValue(Class<?> type, Node node) {
		if (getSupportingPrimitiveTypes().contains(type)) {
			if (type == String.class) {
				String value = toNodeValue(node);
				return value;
			} else if (type == Character.class || type == char.class) {
				String value = toNodeValue(node);
				return value == null || value.length() == 0 ? null : new Character(value.charAt(0));
			} else if (type == Boolean.class || type == boolean.class) {
				String value = toNodeValue(node);
				return Myjung.toBoolean(value);
			} else if (type == Date.class) {
				String value = toNodeValue(node);
				return Myjung.toDate(value);
			} else if (type == Integer.class || type == int.class) {
				String value = toNodeValue(node);
				return new Integer(value);
			} else if (type == Long.class || type == long.class) {
				String value = toNodeValue(node);
				return new Long(value);
			} else if (type == Float.class || type == float.class) {
				String value = toNodeValue(node);
				return new Float(value);
			} else if (type == Double.class || type == double.class) {
				String value = toNodeValue(node);
				return new Double(value);
			}
		} else if (Collection.class.isAssignableFrom(type)) {
			// TODO
		} else if (Map.class.isAssignableFrom(type)) {
			// TODO
		} else if (type.toString().contains(".")) {
			try {
				return populate(node, type.newInstance(), LEVEL_LIMIT);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		return null;
	}

	private static <T> T populateBySax(String from, T to, String level) {
		if (Myjung.isEmpty(from))
			return to;
		setThreadProperty("to", to);
		setThreadProperty("level", level);
		DefaultHandler handler = new DefaultHandler() {
			@SuppressWarnings("unchecked")
			T to = (T) getThreadProperty("to");
			String level = (String) getThreadProperty("level");
			ExtensibleObject eo = to instanceof ExtensibleObject ? (ExtensibleObject) to : null;
			//			Map<String, Object> catFieldMap = categorizeFields(to.getClass(), level, true);
			//			@SuppressWarnings("unchecked")
			//			ListMap<String, Field> attrMap = (ListMap<String, Field>) catFieldMap.get(FIELDCAT_ATTRMAP);
			//			Field textField = (Field) catFieldMap.get(FIELDCAT_TEXTFIELD);
			//			@SuppressWarnings("unchecked")
			//			ListMap<String, Field> elemMap = (ListMap<String, Field>) catFieldMap.get(FIELDCAT_ELEMMAP);
			//			@SuppressWarnings("unchecked")
			//			Map<String, String> singularElemMap = (Map<String, String>) catFieldMap.get(FIELDCAT_SINGULARELEMMAP);

			//			@Override
			//			public void startDocument() throws SAXException {
			//				System.out.println("\r\n\r\nstart document");
			//			}
			//
			//			@Override
			//			public void endDocument() throws SAXException {
			//				System.out.println("\r\nend document");
			//			}

			int depth = 0;

			@Override
			public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
				depth++;

				//				System.out.println("\r\n\tstart element");
				//				System.out.println("\turi: " + uri);
				//				System.out.println("\tlocalName: " + localName);
				//				System.out.println("\tqName: " + qName);

				if (attributes == null || attributes.getLength() == 0)
					return;

				if (depth == 1) {
					int length = attributes.getLength();
					for (int i = 0; i < length; i++) {
						System.out.println("\t\tattribute: " + attributes.getQName(i) + " = " + attributes.getValue(i));
					}
				}
			}

			@Override
			public void endElement(String uri, String localName, String qName) throws SAXException {
				depth--;

				//				System.out.println("\tend element");
				//				System.out.println("\turi: " + uri);
				//				System.out.println("\tlocalName: " + localName);
				//				System.out.println("\tqName: " + qName);
			}
		};
		try {
			getSAXParserFactory().newSAXParser().parse(toInputStreamFromXml(from), handler);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return to;
	}
	public static <T> T populate(Node from, T to, String level) {
		ExtensibleObject eo = to instanceof ExtensibleObject ? (ExtensibleObject) to : null;
		Map<String, Object> catFieldMap = categorizeFields(to.getClass(), level, true);
		@SuppressWarnings("unchecked")
		ListMap<String, Field> attrMap = (ListMap<String, Field>) catFieldMap.get(FIELDCAT_ATTRMAP);
		Field textField = (Field) catFieldMap.get(FIELDCAT_TEXTFIELD);
		@SuppressWarnings("unchecked")
		ListMap<String, Field> elemMap = (ListMap<String, Field>) catFieldMap.get(FIELDCAT_ELEMMAP);
		@SuppressWarnings("unchecked")
		Map<String, String> singularElemMap = (Map<String, String>) catFieldMap.get(FIELDCAT_SINGULARELEMMAP);

		// attributes
		NamedNodeMap attrs = from.getAttributes();
		if (!Myjung.isEmpty(attrs)) {
			int len = attrs.getLength();
			for (int i = 0; i < len; i++) {
				Node attr = attrs.item(i);
				String name = attr.getNodeName();
				if (!attrMap.containsKey(name)) {
					if (eo == null)
						continue;
					eo.setExtendedAttributeValue(name, attr.getNodeValue());
					continue;
				}
				setFieldValue(to, (Field) attrMap.get(name), attr);
			}
		}

		// text & elements
		NodeList childNodes = from.getChildNodes();
		if (childNodes == null || childNodes.getLength() == 0)
			return to;
		Field field;
		Class<?> type;
		short nodeType;
		String name;
		boolean textSet = false;
		for (int i = 0; i < childNodes.getLength(); i++) {
			Node childNode = childNodes.item(i);
			nodeType = childNode.getNodeType();
			if (nodeType == Node.TEXT_NODE || nodeType == Node.CDATA_SECTION_NODE) {
				if (textSet || textField == null)
					continue;
				textSet = true;
				setFieldValue(to, textField, childNode);
			} else if (nodeType == Node.ELEMENT_NODE) {
				name = childNode.getNodeName();
				if (elemMap.containsKey(name)) {
					field = (Field) elemMap.get(name);
					type = field.getType();
					if (Collection.class.isAssignableFrom(type)) {
						MdlElement ann = field.getAnnotation(MdlElement.class);
						if (ann == null || Myjung.isEmpty(ann.singular())) {
							populateCollectionObject(to, field, childNode, type);
						} else {
							List<Node> nodes = getChildElemNodes(childNode);
							if (Myjung.isEmpty(nodes))
								continue;
							for (Node elemNode : nodes)
								populateCollectionObject(to, field, elemNode, field.getType());
						}
					} else {
						setFieldValue(to, field, childNode);
					}
				} else if (singularElemMap.containsKey(name)) {
					name = singularElemMap.get(name);
					field = (Field) elemMap.get(name);
					type = field.getType();
					if (Collection.class.isAssignableFrom(type))
						populateCollectionObject(to, field, childNode, type);
				}
			}
		}

		return to;
	}
	@SuppressWarnings("unchecked")
	private static void populateCollectionObject(Object obj, Field field, Node node, Class<?> type) {
		Class<?> genericType = null;

		MdlElement ann = field.getAnnotation(MdlElement.class);
		if (ann != null && !Myjung.isEmpty(ann.members())) {
			String generic = null;
			String name = node.getNodeName();
			String[] members = ann.members();
			for (String member : members) {
				String[] elemInfo = StringUtils.tokenizeToStringArray(member, ",");
				if (name.equals(elemInfo[0])) {
					generic = elemInfo[1];
					break;
				}
			}
			if (Myjung.isEmpty(generic))
				return;
			try {
				genericType = Class.forName(generic);
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			}
		} else if (field.getGenericType() instanceof ParameterizedType) {
			genericType = (Class<?>) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
		}

		if (genericType == null)
			return;
		Collection<Object> col = null;
		try {
			if (!field.isAccessible())
				field.setAccessible(true);
			if (type == List.class)
				col = (List<Object>) field.get(obj);
			else if (type == Set.class)
				col = (Set<Object>) field.get(obj);
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		if (col == null) {
			if (type == List.class)
				col = new ArrayList<Object>();
			else if (type == Set.class)
				col = new HashSet<Object>();
			setFieldValue(obj, field, col);
		}
		col.add(toValue(genericType, node));
	}
	protected static List<Node> getChildElemNodes(Node node) {
		NodeList nodeList = node.getChildNodes();
		if (nodeList == null || nodeList.getLength() == 0)
			return null;
		List<Node> list = null;
		int length = nodeList.getLength();
		Node childNode = null;
		for (int i = 0; i < length; i++) {
			childNode = nodeList.item(i);
			if (childNode.getNodeType() != Node.ELEMENT_NODE)
				continue;
			if (list == null)
				list = new ArrayList<Node>();
			list.add(childNode);
		}
		return list;
	}

	private final static Class<?>[] primitiveTypes = new Class<?>[] { String.class, Character.class, char.class, Boolean.class, boolean.class,
			Date.class, Integer.class, int.class, Long.class, long.class, Float.class, float.class, Double.class, double.class };
	private static List<Class<?>> primitiveTypeList = toList(primitiveTypes);
	private static List<Class<?>> getSupportingPrimitiveTypes() {
		if (!primitiveTypeList.isEmpty())
			return primitiveTypeList;
		synchronized (primitiveTypeList) {
			if (!primitiveTypeList.isEmpty())
				return primitiveTypeList;
			for (Class<?> type : primitiveTypes)
				primitiveTypeList.add(type);
		}
		return primitiveTypeList;
	}
	///////////////////////////TO////////////////////////////////////////////

	private static InputStream toInputStreamFromXml(String xml) throws Exception {
		if (xml == null)
			return null;
		xml = xml.trim();
		while (xml.startsWith("\r\n")) {
			xml = xml.replaceFirst("\r\n", "");
			xml = xml.trim();
		}
		if (!xml.startsWith("<?xml"))
			xml = new StringBuffer("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n").append(xml).toString();
		return new ByteArrayInputStream(xml.getBytes("UTF-8"));
	}
	public static Document toDocument(String xml) throws Exception {
		InputStream is = toInputStreamFromXml(xml);
		if (is == null)
			return null;
		return getDocumentBuilderFactory().newDocumentBuilder().parse(is);
	}
	private static DocumentBuilderFactory dbf = null;
	private static DocumentBuilderFactory getDocumentBuilderFactory() {
		if (dbf == null) {
			dbf = DocumentBuilderFactory.newInstance();
			//			dbf.setNamespaceAware(false);
			//			dbf.setValidating(false);
		}
		return dbf;
	}
	private static SAXParserFactory spf = null;
	private static SAXParserFactory getSAXParserFactory() {
		if (spf == null) {
			spf = SAXParserFactory.newInstance();
			//			spf.setNamespaceAware(false);
			//			spf.setValidating(false);
		}
		return spf;
	}

	public static String request(String url, List<Property> params, Map<String, Object> options) throws Exception {
		if (url == null)
			return null;
		HttpURLConnection con = null;
		try {
			con = (HttpURLConnection) new URL(url).openConnection();
			if (con == null)
				throw new Exception("Cannot open connection url: " + url);
			if (!Myjung.isEmpty(params)) {
				con.setRequestMethod("POST");
				con.setDoOutput(true);
			}

			con.setReadTimeout(5000);
			con.connect();

			if (!Myjung.isEmpty(params)) {
				StringBuffer buf = new StringBuffer();
				boolean first = true;
				for (Property param : params) {
					String name = param.getName();
					String value = param.getValue();
					if (value == null)
						continue;
					//					con.addRequestProperty(name, value);
					value = URLEncoder.encode(value, "UTF-8");
					if (first) {
						first = false;
					} else {
						buf.append("&");
					}
					buf.append(name).append("=").append(value);
				}
				DataOutputStream dos = new DataOutputStream(con.getOutputStream());
				dos.write(buf.toString().getBytes());
				dos.flush();
			}

			int resCode = con.getResponseCode();
			if (resCode != HttpURLConnection.HTTP_OK)
				throw new Exception("Communication error response code: " + resCode);

			return read(con);

		} catch (IOException e) {
			throw e;
		} finally {
			if (con != null)
				con.disconnect();
		}
	}
	private static String read(HttpURLConnection con) throws Exception {
		Reader reader = new InputStreamReader(con.getInputStream(), "UTF-8");
		StringBuffer buf = new StringBuffer();

		char[] chars = new char[1024];
		int len;
		while ((len = reader.read(chars, 0, 1024)) != -1)
			buf.append(chars, 0, len);

		return buf.toString();
	}

	private static void create(String path, boolean overwrite) throws Exception {
		File file = new File(path);
		File dir = file.getParentFile();
		if (dir != null && !dir.exists())
			dir.mkdirs();
		if (file.exists() && !overwrite)
			throw new Exception("The file is already exist path:" + file.getAbsolutePath());
		file.createNewFile();
	}
	private static final DateFormat DATEFORMAT_LOGDATE = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss,S", Locale.US);
	private static boolean isLogDate(String str) {
		try {
			if (DATEFORMAT_LOGDATE.parse(str) == null)
				return false;
			return true;
		} catch (Exception e) {
			return false;
		}
	}
	public static String readLog(String path, long size, boolean asc, Map<String, Object> options) throws Exception {
		if (size <= 0)
			return null;
		File file = new File(path);
		if (!file.exists())
			return null;
		RandomAccessFile raf = null;
		try {
			StringBuffer buf = new StringBuffer();
			StringBuffer gBuf = null;
			raf = new RandomAccessFile(file, "r");
			if (raf.length() > size)
				raf.seek(raf.length() - size);
			long line = 0;
			String str;
			StringBuffer buf2 = null;
			List<StringBuffer> list = null;
			while ((str = raf.readLine()) != null) {
				if (line == 0) {
					if (Myjung.isEmpty(str))
						continue;
					if (str.length() < 24 || !Myjung.isLogDate(str.substring(0, 23))) {
						if (gBuf == null)
							gBuf = new StringBuffer();
						else
							gBuf.append("\r\n");
						gBuf.append(str);
						continue;
					}
					line++;
				} else if (asc) {
					buf.append("\r\n");
				}

				if (asc) {
					buf.append(str);
					continue;
				}

				if (list == null)
					list = new ArrayList<StringBuffer>();

				if (str.length() > 23 && Myjung.isLogDate(str.substring(0, 23))) {
					if (buf2 != null)
						list.add(0, buf2);
					buf2 = new StringBuffer();
				} else if (buf2 == null) {
					buf2 = new StringBuffer();
				} else if (buf2.length() != 0)
					buf2.append("\r\n");
				buf2.append(str);
			}

			if (!asc && buf2 != null) {
				buf.append(buf2);
				if (!Myjung.isEmpty(list)) {
					for (StringBuffer buf3 : list)
						buf.append("\r\n").append(buf3);
				}
			}
			return buf.length() == 0 && gBuf != null ? gBuf.toString() : buf.toString();
		} catch (FileNotFoundException e) {
			if (logger.isInfoEnabled())
				logger.info("No such file: " + file.getAbsolutePath());
			return null;
		} catch (Exception e) {
			throw e;
		} finally {
			close(raf);
		}
	}
	private static File getFile(String path) {
		try {
			return ResourceUtils.getFile(path);
		} catch (FileNotFoundException e) {
			return new File(path);
		}
	}
	public static String readFileString(String path) throws Exception {
		File file = getFile(path);
		if (!file.exists())
			return null;
		InputStreamReader fr = null;
		BufferedReader br = null;
		try {
			StringBuffer buf = new StringBuffer();
			fr = new InputStreamReader(new FileInputStream(file), "UTF-8");
			br = new BufferedReader(fr);
			long line = 0;
			String str;
			while ((str = br.readLine()) != null) {
				if (line++ != 0)
					buf.append("\r\n");
				buf.append(str);
			}
			return buf.toString();
		} catch (FileNotFoundException e) {
			if (logger.isInfoEnabled())
				logger.info("No such file: " + file.getAbsolutePath());
			return null;
		} catch (Exception e) {
			throw e;
		} finally {
			close(br);
			close(fr);
		}
	}
	public static void writeFileString(String path, String text, boolean overwrite) throws Exception {
		FileWriter fw = null;
		BufferedWriter bw = null;
		try {
			create(path, overwrite);
			fw = new FileWriter(path, false);
			bw = new BufferedWriter(fw);
			bw.write(text);
			bw.flush();
			fw.flush();
		} catch (Exception e) {
			throw e;
		} finally {
			close(bw);
			close(fw);
		}
	}
	public static void deleteAll(String path) {
		if (Myjung.isEmpty(path))
			return;
		//		deleteAll(new File(path));
		deleteAll(getFile(path));
	}
	private static void deleteAll(File file) {
		if (!file.exists())
			return;
		if (file.isDirectory()) {
			File[] files = file.listFiles();
			if (!Myjung.isEmpty(files)) {
				int length = files.length;
				for (int i = 0; i < length; i++) {
					File subFile = files[i];
					deleteAll(subFile);
				}
			}
		}
		file.delete();
	}
	public static void copyAll(String from, String to, boolean overwrite) throws Exception {
		if (Myjung.isEmpty(from))
			return;
		copyAll(getFile(from), to, overwrite);
	}
	private static void copyAll(File fromFile, String to, boolean overwrite) throws Exception {
		if (!fromFile.exists())
			throw new Exception("not exist file: " + fromFile.getAbsolutePath());

		if (fromFile.isDirectory()) {
			File toFile = new File(to);
			if (!toFile.exists())
				toFile.mkdirs();

			File[] files = fromFile.listFiles();
			if (Myjung.isEmpty(files))
				return;

			int fileLength = files.length;
			for (int i = 0; i < fileLength; i++) {
				File file = files[i];
				copyAll(file, new StringBuffer(to).append("/").append(file.getName()).toString(), overwrite);
			}
			return;
		}

		copy(fromFile, to, overwrite);
	}
	private static void copy(File file, String to, boolean overwrite) throws Exception {
		if (!file.exists())
			return;
		create(to, overwrite);
		FileInputStream is = null;
		FileOutputStream os = null;
		FileChannel ic = null;
		FileChannel oc = null;
		try {
			is = new FileInputStream(file);
			os = new FileOutputStream(to);
			ic = is.getChannel();
			oc = os.getChannel();
			long size = ic.size();
			ic.transferTo(0, size, oc);
		} catch (Exception e) {
			throw e;
		} finally {
			close(oc);
			close(ic);
			close(os);
			close(is);
		}
	}
	public static void zip(String path, String zipPath, Map<String, Object> options) throws Exception {
		if (Myjung.isEmpty(zipPath))
			zipPath = path + ".zip";
		List<File> files = extractFiles(path);
		ZipOutputStream os = null;
		InputStream is = null;
		try {
			os = new ZipOutputStream(new FileOutputStream(new File(path + ".zip")));
			CRC32 crc = new CRC32();

			if (!Myjung.isEmpty(files)) {
				String[] rootPaths = StringUtils.tokenizeToStringArray(path, "/");
				String rootPath = rootPaths[rootPaths.length - 1];
				rootPath = path.substring(0, path.indexOf(rootPath)).replace("\\", "/");
				int len;
				byte[] buffer = new byte[10240];
				for (File file : files) {
					if (!file.exists())
						continue;

					is = new BufferedInputStream(new FileInputStream(file));
					crc.reset();
					while ((len = is.read(buffer, 0, buffer.length)) != -1)
						crc.update(buffer, 0, len);
					is.close();

					is = new BufferedInputStream(new FileInputStream(file));
					String zipName = file.getAbsolutePath();
					zipName = zipName.replace("\\", "/");
					int index = zipName.indexOf(rootPath);
					if (index != -1)
						zipName = zipName.substring(index + rootPath.length());

					ZipEntry entry = new ZipEntry(zipName);
					entry.setMethod(ZipEntry.STORED);
					entry.setCompressedSize(file.length());
					entry.setSize(file.length());
					entry.setCrc(crc.getValue());
					os.putNextEntry(entry);
					while ((len = is.read(buffer, 0, buffer.length)) != -1)
						os.write(buffer, 0, len);
					is.close();
				}
			}

		} catch (Exception e) {
			throw e;
		} finally {
			if (is != null)
				is.close();
			if (os != null) {
				os.flush();
				os.close();
			}
		}
	}
	private static List<File> extractFiles(String path) throws Exception {
		List<File> list = new ArrayList<File>();
		populateFileList(list, new File(path));
		return list;
	}
	private static void populateFileList(List<File> files, File file) throws Exception {
		if (file.isDirectory()) {
			File[] subFiles = file.listFiles();
			for (File subFile : subFiles)
				populateFileList(files, subFile);
		} else {
			files.add(file);
		}
	}
	public static Properties getProperties(String propsPath) throws Exception {
		if (Myjung.isEmpty(propsPath))
			return null;
		return load(propsPath);
	}
	public static String getProperty(String key, String propsPath) throws Exception {
		if (Myjung.isEmpty(key) || Myjung.isEmpty(propsPath))
			return null;
		Properties props = load(propsPath);
		if (props.containsKey(key)) {
			return props.getProperty(key);
		} else {
			key = checkKeyReservedWord(key);
			return props.getProperty(key);
		}
	}
	public static void setProperty(String key, String value, String propsPath) throws Exception {
		setProperties(key + "=" + value, propsPath);
	}
	public static void setProperties(String propsStr, String propsPath) throws Exception {
		doProperties(propsStr, propsPath, "set");
	}
	public static void removeProperties(String propsStr, String propsPath) throws Exception {
		doProperties(propsStr, propsPath, "remove");
	}
	private static Properties load(String propsPath) throws Exception {
		Properties props = new Properties();
		props.load(new FileInputStream(propsPath));
		return props;
	}
	private static void store(Properties props, String propsPath) throws Exception {
		props.store(new FileOutputStream(propsPath), null);
	}
	private static Properties toProperties(String propsStr) throws Exception {
		propsStr = checkReservedWord(propsStr);
		ByteArrayInputStream bis = new ByteArrayInputStream(propsStr.getBytes());
		Properties props = new Properties();
		props.load(bis);
		return props;
	}
	private static String checkKeyReservedWord(String str) throws Exception {
		str = StringUtils.replace(str, "\\:", ":");
		str = StringUtils.replace(str, "\\", "/");
		return str;
	}
	private static String checkReservedWord(String str) throws Exception {
		str = checkReservedWord(str, ":");
		String colonId = Myjung.newId();
		str = StringUtils.replace(str, "\\:", colonId);
		str = StringUtils.replace(str, "\\", "/");
		str = StringUtils.replace(str, colonId, "\\:");
		return str;
	}
	private static String checkReservedWord(String str, String checkWord) throws Exception {
		String word = "\\" + checkWord;
		String tmpWord = Myjung.newId();
		if (str.indexOf(word) != -1)
			str = StringUtils.replace(str, word, tmpWord);
		if (str.indexOf(checkWord) != -1)
			str = StringUtils.replace(str, checkWord, word);
		if (str.indexOf(tmpWord) != -1)
			str = StringUtils.replace(str, tmpWord, word);
		return str;
	}
	private static void doProperties(String propsStr, String propsPath, String method) throws Exception {
		if (Myjung.isEmpty(propsStr) || Myjung.isEmpty(propsPath))
			return;

		Properties newProps = toProperties(propsStr);
		if (newProps.isEmpty())
			return;

		File propFile = new File(propsPath);
		Properties props = null;
		if (propFile.exists()) {
			props = load(propsPath);
		} else {
			props = new Properties();
		}

		if (method.equalsIgnoreCase("set")) {
			props.putAll(newProps);
		} else if (method.equalsIgnoreCase("remove")) {
			if (!props.isEmpty()) {
				for (Object key : newProps.keySet())
					props.remove(key);
			}
		}

		store(props, propsPath);
	}
	public static void close(Closeable obj) {
		if (obj == null)
			return;
		try {
			obj.close();
		} catch (Exception e) {
			logger.warn(e, e);
		}
	}
}

final class UUIdGenerator {
	private static UUIdGenerator instance;
	private static int IP = 0;
	private static short counter = (short) 0;
	private static final int JVM = (int) (System.currentTimeMillis() >>> 8);
	public static UUIdGenerator getInstance() {
		if (instance == null)
			instance = new UUIdGenerator();
		return instance;
	}
	private static int toInt(byte[] bytes) {
		int result = 0;
		for (int i = 0; i < 4; i++) {
			result = (result << 8) - Byte.MIN_VALUE + (int) bytes[i];
		}
		return result;
	}
	private String format(int intval) {
		String formatted = Integer.toHexString(intval);
		StringBuffer buf = new StringBuffer("00000000");
		buf.replace(8 - formatted.length(), 8, formatted);
		return buf.toString();
	}
	private String format(short shortval) {
		String formatted = Integer.toHexString(shortval);
		StringBuffer buf = new StringBuffer("0000");
		buf.replace(4 - formatted.length(), 4, formatted);
		return buf.toString();
	}
	private short getHiTime() {
		return (short) (System.currentTimeMillis() >>> 32);
	}
	private int getLoTime() {
		return (int) System.currentTimeMillis();
	}
	private short getCount() {
		synchronized (UUIdGenerator.class) {
			if (counter < 0)
				counter = 0;
			return counter++;
		}
	}
	public String generateId() {
		return new StringBuffer(32).append(format(JVM)).append(format(getHiTime())).append(format(getLoTime())).append(format(getCount())).toString();
	}
	public String generateUId() {
		if (IP == 0) {
			int ipadd;
			try {
				ipadd = toInt(InetAddress.getLocalHost().getAddress());
			} catch (Exception e) {
				ipadd = 0;
			}
			IP = ipadd;
		}
		return new StringBuffer(32).append(format(IP)).append(format(JVM)).append(format(getHiTime())).append(format(getLoTime()))
				.append(format(getCount())).toString();
	}
}

class DateInfo {
	private String year;
	private String month;
	private String day;
	private String hour;
	private String minute;
	private String second;
	private String millisecond;
	public DateInfo(Date date) {
		Calendar calendar = Calendar.getInstance();
		calendar.setTime(date);

		year = calendar.get(Calendar.YEAR) + "";
		month = calendar.get(Calendar.MONTH) + 1 + "";
		day = calendar.get(Calendar.DAY_OF_MONTH) + "";
		hour = calendar.get(Calendar.HOUR_OF_DAY) + "";
		minute = calendar.get(Calendar.MINUTE) + "";
		second = calendar.get(Calendar.SECOND) + "";
		millisecond = calendar.get(Calendar.MILLISECOND) + "";

		if (month.length() == 1)
			month = "0" + month;
		if (day.length() == 1)
			day = "0" + day;
		if (hour.length() == 1)
			hour = "0" + hour;
		if (minute.length() == 1)
			minute = "0" + minute;
		if (second.length() == 1)
			second = "0" + second;
		switch (millisecond.length()) {
		case 1:
			millisecond = "00" + millisecond;
			break;
		case 2:
			millisecond = "0" + millisecond;
			break;
		}
	}
	public String getDay() {
		return day;
	}
	public void setDay(String day) {
		this.day = day;
	}
	public String getHour() {
		return hour;
	}
	public void setHour(String hour) {
		this.hour = hour;
	}
	public String getMillisecond() {
		return millisecond;
	}
	public void setMillisecond(String millisecond) {
		this.millisecond = millisecond;
	}
	public String getMinute() {
		return minute;
	}
	public void setMinute(String minute) {
		this.minute = minute;
	}
	public String getMonth() {
		return month;
	}
	public void setMonth(String month) {
		this.month = month;
	}
	public String getSecond() {
		return second;
	}
	public void setSecond(String sceond) {
		this.second = sceond;
	}
	public String getYear() {
		return year;
	}
	public void setYear(String year) {
		this.year = year;
	}
}